﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;
using Microsoft.Xna.Framework.Content;
using Datorgrafik_lab2.Components.Input;

namespace Datorgrafik_lab2.Objects
{
    /// <summary>
    /// Helper class for drawing a tank model with animated wheels and turret.
    /// http://xbox.create.msdn.com/en-US/education/catalog/sample/simple_animation
    /// http://xbox.create.msdn.com/en-US/education/catalog/sample/collision_3d_heightmap_normals
    /// </summary>
    public class Tank : PlayerObject
    {
        #region Fields


        Quaternion Heading;

        Matrix Height;

        // The XNA framework Model object that we are going to display.
        Model tankModel;

        // Shortcut references to the bones that we are going to animate.
        // We could just look these up inside the Draw method, but it is more
        // efficient to do the lookups while loading and cache the results.
        ModelBone leftBackWheelBone;
        ModelBone rightBackWheelBone;
        ModelBone leftFrontWheelBone;
        ModelBone rightFrontWheelBone;
        ModelBone leftSteerBone;
        ModelBone rightSteerBone;
        ModelBone turretBone;
        ModelBone cannonBone;
        ModelBone hatchBone;

        // Store the original transform matrix for each animating bone.
        Matrix leftBackWheelTransform;
        Matrix rightBackWheelTransform;
        Matrix leftFrontWheelTransform;
        Matrix rightFrontWheelTransform;
        Matrix leftSteerTransform;
        Matrix rightSteerTransform;
        Matrix turretTransform;
        Matrix cannonTransform;
        Matrix hatchTransform;

        // Array holding all the bone transform matrices for the entire model.
        // We could just allocate this locally inside the Draw method, but it
        // is more efficient to reuse a single array, as this avoids creating
        // unnecessary garbage.
        Matrix[] boneTransforms;

        // Current animation positions.
        float wheelRotationValue;
        float steerRotationValue;
        float turretRotationValue;
        float cannonRotationValue;
        float hatchRotationValue;

        #endregion
        #region Properties

        /// <summary>
        /// Gets or sets the wheel rotation amount.
        /// </summary>
        public float WheelRotation
        {
            get { return wheelRotationValue; }
            set { wheelRotationValue = value; }
        }

        /// <summary>
        /// Gets or sets the steering rotation amount.
        /// </summary>
        public float SteerRotation
        {
            get { return steerRotationValue; }
            set { steerRotationValue = value; }
        }

        /// <summary>
        /// Gets or sets the turret rotation amount.
        /// </summary>
        public float TurretRotation
        {
            get { return turretRotationValue; }
            set { turretRotationValue = value; }
        }

        /// <summary>
        /// Gets or sets the cannon rotation amount.
        /// </summary>
        public float CannonRotation
        {
            get { return cannonRotationValue; }
            set { cannonRotationValue = value; }
        }

        /// <summary>
        /// Gets or sets the entry hatch rotation amount.
        /// </summary>
        public float HatchRotation
        {
            get { return hatchRotationValue; }
            set { hatchRotationValue = value; }
        }

        #endregion

        public Tank(Matrix worldMatrix) : base(worldMatrix)
        { }

        /// <summary>
        /// Loads the tank model.
        /// </summary>
        public void Load(ContentManager content)
        {
            // Load the tank model from the ContentManager.
            tankModel = content.Load<Model>("Player/tank");

            tankModel.Root.Transform = Matrix.CreateScale(Scale)
                * Matrix.CreateRotationZ(MathHelper.ToRadians(90))
                * Matrix.CreateRotationX(MathHelper.ToRadians(-90))
                * Matrix.CreateTranslation(this.Position);

            Heading = Quaternion.CreateFromAxisAngle(Vector3.Up, MathHelper.ToRadians(-90));

            // Look up shortcut references to the bones we are going to animate.
            leftBackWheelBone = tankModel.Bones["l_back_wheel_geo"];
            rightBackWheelBone = tankModel.Bones["r_back_wheel_geo"];
            leftFrontWheelBone = tankModel.Bones["l_front_wheel_geo"];
            rightFrontWheelBone = tankModel.Bones["r_front_wheel_geo"];
            leftSteerBone = tankModel.Bones["l_steer_geo"];
            rightSteerBone = tankModel.Bones["r_steer_geo"];
            turretBone = tankModel.Bones["turret_geo"];
            cannonBone = tankModel.Bones["canon_geo"];
            hatchBone = tankModel.Bones["hatch_geo"];

            Rotation = Quaternion.Identity;
            Height = Matrix.Identity;

            // Store the original transform matrix for each animating bone.
            leftBackWheelTransform = leftBackWheelBone.Transform;
            rightBackWheelTransform = rightBackWheelBone.Transform;
            leftFrontWheelTransform = leftFrontWheelBone.Transform;
            rightFrontWheelTransform = rightFrontWheelBone.Transform;
            leftSteerTransform = leftSteerBone.Transform;
            rightSteerTransform = rightSteerBone.Transform;
            turretTransform = turretBone.Transform;
            cannonTransform = cannonBone.Transform;
            hatchTransform = hatchBone.Transform;

            // Allocate the transform matrix array.
            boneTransforms = new Matrix[tankModel.Bones.Count];
        }

        public void performAction(ActionType action, float elapsedTime)
        {
            switch (action)
            {
                case ActionType.RollLeft: // Left
                    this.pos2d.X -= elapsedTime * speed;
                    if (pos2d.X <= 0) pos2d.X = 360 - pos2d.X;
                    break;
                case ActionType.RollRight: // Right
                    this.pos2d.X += elapsedTime * speed;
                    this.pos2d.X = this.pos2d.X % 360;
                    break;
                case ActionType.PitchUp: // Backward
                    //this.Position.Z += elapsedTime * speed;
                    this.pos2d.Y += 1;
                    this.pos2d.Y = this.pos2d.Y % 360;
                    break;
                case ActionType.PitchDown: // Forward
                    this.pos2d.Y -= elapsedTime * speed;
                    if (pos2d.Y <= 0) pos2d.Y = 360 - pos2d.Y;
                    break;
                case ActionType.IncreaseSpeed:
                    this.IncreaseSpeed(1);
                    break;
                case ActionType.DecreaseSpeed:
                    this.DecreaseSpeed(1);
                    break;
            }
        }

        public void Update(GameTime gameTime)
        {
            float time = (float)gameTime.TotalGameTime.TotalSeconds;

            // Update the animation properties on the tank object. In a real game
            // you would probably take this data from user inputs or the physics
            // system, rather than just making everything rotate like this!

            this.WheelRotation = time * speed;
            //this.SteerRotation = (float)Math.Sin(time * 0.75f) * 0.5f;
            //this.TurretRotation = (float)Math.Sin(time * 0.333f) * 1.25f;
            //this.CannonRotation = (float)Math.Sin(time * 0.25f) * 0.333f - 0.333f;
            //this.HatchRotation = MathHelper.Clamp((float)Math.Sin(time * 2) * 2, -1, 0);

            Quaternion sphereRot = Quaternion.Identity;

            float pitch = 0;
            float roll = 0;
            float yaw = 0;
            UpdateYawPitchRoll(out yaw, out pitch, out roll);
            sphereRot = Quaternion.CreateFromYawPitchRoll(yaw, pitch, roll);

            this.Rotation = this.Heading * sphereRot;
        }


        /// <summary>
        /// Draws the tank model, using the current animation settings.
        /// </summary>
        public void Draw(Matrix world, Matrix view, Matrix projection)
        {
            // Set the world matrix as the root transform of the model.
            tankModel.Root.Transform = world * Matrix.CreateScale(Scale) *
                        //Matrix.CreateFromQuaternion(this.Heading) *
                        this.Height *
                        Matrix.CreateFromQuaternion(this.Rotation) *                        
                        Matrix.CreateTranslation(this.Position);

            // Calculate matrices based on the current animation position.
            Matrix wheelRotation = Matrix.CreateRotationX(wheelRotationValue);
            Matrix steerRotation = Matrix.CreateRotationY(steerRotationValue);
            Matrix turretRotation = Matrix.CreateRotationY(turretRotationValue);
            Matrix cannonRotation = Matrix.CreateRotationX(cannonRotationValue);
            Matrix hatchRotation = Matrix.CreateRotationX(hatchRotationValue);

            // Apply matrices to the relevant bones.
            leftBackWheelBone.Transform = wheelRotation * leftBackWheelTransform;
            rightBackWheelBone.Transform = wheelRotation * rightBackWheelTransform;
            leftFrontWheelBone.Transform = wheelRotation * leftFrontWheelTransform;
            rightFrontWheelBone.Transform = wheelRotation * rightFrontWheelTransform;
            leftSteerBone.Transform = steerRotation * leftSteerTransform;
            rightSteerBone.Transform = steerRotation * rightSteerTransform;
            turretBone.Transform = turretRotation * turretTransform;
            cannonBone.Transform = cannonRotation * cannonTransform;
            hatchBone.Transform = hatchRotation * hatchTransform;

            // Look up combined bone matrices for the entire model.
            tankModel.CopyAbsoluteBoneTransformsTo(boneTransforms);

            // Draw the model.
            foreach (ModelMesh mesh in tankModel.Meshes)
            {
                foreach (BasicEffect effect in mesh.Effects)
                {
                    effect.World = boneTransforms[mesh.ParentBone.Index];
                    
                    effect.View = view;
                    effect.Projection = projection;

                    effect.EnableDefaultLighting();
                }

                mesh.Draw();
            }
        }

        public Vector3 GetPositionOnMap()
        {
            return new Vector3(pos2d.X, pos2d.Y, 1);
        }

        private void changeHeading(Directions from, Directions to) {
            this.Heading *= Quaternion.CreateFromAxisAngle(Vector3.Up, MathHelper.ToRadians(-(to-from) * 90));
        }

        public void HandleInput(Components.InputManager _input, float elapsedTime)
        {
            Vector2 movement = Vector2.Zero;

            // Get what to do
            if (_input.IsUpPressed())
            {
                movement.Y = elapsedTime * speed * 0.1f;
            } else if(_input.IsDownPressed()) 
            {
                //movement.Y = -(elapsedTime * speed * 0.1f);
            }
            else if (_input.IsLeftPressed())
            {
                if (direction == Directions.North) {
                    direction = Directions.West;
                    changeHeading(Directions.North, Directions.West);
                }
                else if (direction == Directions.West) {
                    direction = Directions.South;
                    changeHeading(Directions.West, Directions.South);
                }
                else if (direction == Directions.South) {
                    direction = Directions.East;
                    changeHeading(Directions.South, Directions.East);
                }
                else if (direction == Directions.East) { 
                    direction = Directions.North;
                    changeHeading(Directions.East, Directions.North);
                }
            }
            else if (_input.IsRightPressed())
            {
                if(direction == Directions.North) {
                    direction = Directions.East;
                    changeHeading(Directions.North, Directions.East);
                }
                else if (direction == Directions.East) {
                    direction = Directions.South;
                    changeHeading(Directions.East, Directions.South);
                }
                else if (direction == Directions.South) {
                    direction = Directions.West;
                    changeHeading(Directions.South, Directions.West);
                }
                else if (direction == Directions.West) {
                    direction = Directions.North;
                    changeHeading(Directions.West, Directions.North);
                }
            }

            // What direction
            switch (direction)
            {
                case Directions.North:
                    this.pos2d.Y += -movement.Y;
                    this.pos2d.X += -movement.X;
                    break;
                case Directions.East:
                    this.pos2d.Y += -movement.X;
                    this.pos2d.X += movement.Y;
                    break;
                case Directions.South:
                    this.pos2d.Y += movement.Y;
                    this.pos2d.X += movement.X;
                    break;
                case Directions.West:
                    this.pos2d.Y += movement.X;
                    this.pos2d.X += -movement.Y;
                    break;
            }

            // Make sure they are inside the map
            if (pos2d.Y < 0 || pos2d.Y >= Ground.numRow)
            {
                pos2d.X += Ground.numCol / 2;

                if (pos2d.Y < 0)
                    pos2d.Y = 1;
                if(pos2d.Y >= Ground.numRow)
                    pos2d.Y = Ground.numRow - 2;
                if (direction == Directions.North) 
                    direction = Directions.South;
                else if (direction == Directions.South) 
                    direction = Directions.North;
            }

            if (pos2d.X < 0) 
                pos2d.X = pos2d.X + (Ground.numCol-1);
            if (pos2d.X >= Ground.numCol)
                pos2d.X -= Ground.numCol;
        }

        public void UpdateYawPitchRoll(out float pitch, out float roll, out float yaw)
        {
            yaw = 0;
            pitch = 0f; // Direction - Already defined
            roll = 0;

            if(direction == Directions.South)
                roll = MathHelper.Pi * 2 * ((pos2d.Y) / 200);
            else if(direction == Directions.North)
                roll = -MathHelper.Pi * 2 * ((pos2d.Y) / 200);
            else if (direction == Directions.East)
            {
                float tmp = pos2d.Y;
                if (tmp > 50)
                    tmp -= 100 * -1;
                tmp /= 50;
                roll = MathHelper.Pi * 2 * ((pos2d.X) / 300) * tmp;
                yaw = MathHelper.Pi * 2 * ((pos2d.Y) / 200);
            }
            else if (direction == Directions.West)
            {
                float tmp = pos2d.Y;
                if (tmp > 50)
                    tmp -= 100 * -1;
                tmp /= 50;
                roll = MathHelper.Pi * 2 * ((pos2d.X) / 200) * tmp;
                yaw = -MathHelper.Pi * 2 * ((pos2d.Y) / 200);
            }
        }
    }
}